/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.kernel;

/**
 * This class represents possible values. Its instances are not mutable. <br/>
 * It is thus a kind of <i>sum type</i> as a value can be:
 * <ul>
 *   <li>an integer <i>x</i>, stored as <i>(x &lt;&lt; 1) | 1</i> <br/>
 *       (such a value is also referred to as a "long", following the
 *       caml vocabulary);</li>
 *   <li>a reference to a <tt>Block</tt>;</li>
 *   <li>a code offset <i>ofs</i>, stored as <i>ofs &lt;&lt; 1</i>.</li>
 * </ul>
 * <br/>
 * Values representing long values may be cached. More, it is guaranteed
 * across versions that caching will be enabled at least for the interval
 * from <tt>-128</tt> to <tt>255</tt> (exact bounds for the current version
 * are given by {@link #MIN_CACHED_VALUE} and {@link #MAX_CACHED_VALUE}).
 * Practically, this means that comparison with a (long) value in this interval
 * can be done using <tt>==</tt> operator over value instances rather than
 * using {@link #asLong()} method.
 *
 * @see fr.x9c.cadmium.kernel.Block
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
public final class Value {

    /** "Unit" constant (cached as unit is 0). */
    public static final Value UNIT;

    /** "False" constant (cached as false is 0). */
    public static final Value FALSE;

    /** "True" constant (cached as true is 1). */
    public static final Value TRUE;

    /** "Empty list" constant (cached as empty list is 0). */
    public static final Value EMPTY_LIST;

    /** "Zero" constant (cached). */
    public static final Value ZERO;

    /** "One" constant (cached). */
    public static final Value ONE;

    /** "Two" constant (cached). */
    public static final Value TWO;

    /** "Minus one" constant (cached). */
    public static final Value MINUS_ONE;

    /** "Minus two" constant (cached). */
    public static final Value MINUS_TWO;

    /** Maximum 'long' value. */
    public static final int MAX_LONG = (1 << 30) - 1;

    /** Minimum 'long' value. */
    public static final int MIN_LONG = -(1 << 30);

    /** Constant for "unit" value. */
    private static final int UNIT_AS_LONG = 0;

    /** Constant for "false" value. */
    private static final int FALSE_AS_LONG = 0;

    /** Constant for "true" value. */
    private static final int TRUE_AS_LONG = 1;

    /** Constant for "empty list" value. */
    private static final int EMPTY_LIST_AS_LONG = 0;

    /** Minimum long value in cache. */
    private static final int MIN_CACHED_VALUE = -128;

    /** Maximum long value in cache. */
    private static final int MAX_CACHED_VALUE = 255;

    /** Array of cached values. */
    private static final Value[] CACHED_VALUES;

    /** Integer cardinality. */
    private static final long INTEGER_CARDINALITY =
        ((long) Integer.MAX_VALUE) - ((long) Integer.MIN_VALUE) + 1;

    /** Value as an integer, not defined if the value is a block value. */
    private final int longValue;

    /** Value as a block, <tt>null</tt> if the value is a long value. */
    private final Block blockValue;

    /* Cache initialization. */
    static {
        CACHED_VALUES =
            new Value[Value.MAX_CACHED_VALUE - Value.MIN_CACHED_VALUE + 1];
        for (int i = Value.MIN_CACHED_VALUE; i <= Value.MAX_CACHED_VALUE; i++) {
            Value.CACHED_VALUES[i - Value.MIN_CACHED_VALUE] =
                new Value((i << 1) | 1);
        } // end for
        UNIT = Value.createFromLong(Value.UNIT_AS_LONG);
        FALSE = Value.createFromLong(Value.FALSE_AS_LONG);
        TRUE = Value.createFromLong(Value.TRUE_AS_LONG);
        EMPTY_LIST = Value.createFromLong(Value.EMPTY_LIST_AS_LONG);
        ZERO = Value.createFromLong(0);
        ONE = Value.createFromLong(1);
        TWO = Value.createFromLong(2);
        MINUS_ONE = Value.createFromLong(-1);
        MINUS_TWO = Value.createFromLong(-2);
    } // end static block

    /**
     * Constructs a value from block.
     * @param b block defining value - should not be <tt>null</tt>
     */
    private Value(final Block b) {
        assert b != null : "null b";
        this.longValue = -1;
        this.blockValue = b;
    } // end constructor(Block)

    /**
     * Constructs a value from a long value.
     * @param l raw long value
     */
    private Value(final int l) {
        this.longValue = l;
        this.blockValue = null;
    } // end constructor(int)

    /**
     * Creates a long value.
     * @param v long value
     * @return the wrapped long value
     */
    public static Value createFromLong(final int v) {
        if ((v >= Value.MIN_CACHED_VALUE) && (v <= Value.MAX_CACHED_VALUE)) {
            return Value.CACHED_VALUES[v - Value.MIN_CACHED_VALUE];
        } else {
            return new Value((v << 1) | 1);
        } // end if/else
    } // end method 'createFromLong(int)'

    /**
     * Creates a code offset value.
     * @param ofs code offset value
     * @return the wrapped code offset value
     */
    public static Value createFromCodeOffset(final int ofs) {
            return new Value(ofs << 1);
    } // end method 'createFromCodeOffset(int)'

    /**
     * Creates a block value.
     * @param b block - should not be <tt>null</tt>
     * @return the wrapped block value
     */
    public static Value createFromBlock(final Block b) {
        assert b != null : "null b";
        return new Value(b);
    } // end method 'createFromBlock(Block)'

    /**
     * Creates a long or code offset value from an integer,
     * making no conversion.
     * @param value raw long value
     * @return the wrapped raw value
     */
    public static Value createFromRawValue(final int value) {
        if ((value & 1) != 0) {
            return createFromLong(value >> 1);
        } else {
            return new Value(value);
        } // end if/else
    } // end method 'createFromRawValue(int)'

    /**
     * Tests whether the value is a long value.
     * @return <tt>true</tt> if the value is a long value,
     *         <tt>false</tt> otherwise
     */
    public boolean isLong() {
        return (this.blockValue == null)
            && ((this.longValue & 1) != 0);
    } // end method 'isLong()'

    /**
     * Tests whether the value is a code offset value.
     * @return <tt>true</tt> if the value is a code offset value,
     *         <tt>false</tt> otherwise
     */
    public boolean isCodeOffset() {
        return (this.blockValue == null)
            && ((this.longValue & 1) == 0);
    } // end method 'isCodeOffset()'

    /**
     * Tests whether the value is a block value.
     * @return <tt>true</tt> if the value is a block value,
     *         <tt>false</tt> otherwise
     */
    public boolean isBlock() {
        return this.blockValue != null;
    } // end method 'isBlock()'

    /**
     * Returns the value stored in the 'long' slot, with no conversion. <br/>
     * Should only be called on a integer-stored value (either long or code
     * offset).
     * @return the value stored in the 'long' slot, with no conversion
     */
    public int getRawValue() {
        assert !isBlock() : "not a integer-stored value";
        return this.longValue;
    } // end method 'getRawValue()'

    /**
     * Returns the long stored in the value. <br/>
     * Should only be called on a 'long' value.
     * @return the long stored in the value, an unspecified non-zero value if
     *         the value is a block
     */
    public int asLong() {
        return this.longValue >> 1;
    } // end method 'asLong()'

    /**
     * Returns the code offset stored in the value. <br/>
     * Should only be called on a 'code offset' value.
     * @return the code offset stored in the value
     */
    public int asCodeOffset() {
        assert isCodeOffset() : "not a code offset value";
        return this.longValue >> 1;
    } // end method 'asCodeOffset()'

    /**
     * Returns the block stored in the value. <br/>
     * Should only be called on a 'block' value.
     * @return the block stored in the value
     */
    public Block asBlock() {
        assert isBlock() : "not a block value";
        return this.blockValue;
    } // end method 'asBlock()'

    /**
     * Converts a value such that:
     * <ul>
     *  <li>two <i>long</i>, <i>code</i> or <i>block</i> values are
     *      always accurately compared;</li>
     *  <li>two values of different kinds are arbitrary but consistenly
     *      compared (this means that result is arbitrary but respects
     *      order properties).</li>
     * </ul>
     * @return converted value, as a signed value
     */
    public long universalLong() {
        if (isBlock()) {
            return ((long) Integer.MIN_VALUE)
                - this.blockValue.getMagicNumber();
        } else if (isLong()) {
            return this.longValue >> 1;
        } else {
            return Value.INTEGER_CARDINALITY + ((long) (this.longValue >> 1));
        } // end if/elsif/else
    } // end method 'universalLong()'

    /**
     * Converts a value such that:
     * <ul>
     *  <li>two <i>long</i>, <i>code</i> or <i>block</i> values are
     *      always accurately compared;</li>
     *  <li>two values of different kinds are arbitrary but consistenly
     *      compared (this means that result is arbitrary but respects
     *      order properties).</li>
     * </ul>
     * <b>A <i>long</i> value is unsigned.</b>
     * @return converted value, as an unsigned value
     */
    public long universalUnsignedLong() {
        if (isBlock()) {
            return ((long) Integer.MIN_VALUE)
                - this.blockValue.getMagicNumber();
        } else if (isLong()) {
            return fr.x9c.cadmium.util.Misc.signedToUnsigned(this.longValue >> 1);
        } else {
            return Value.INTEGER_CARDINALITY + ((long) (this.longValue >> 1));
        } // end if/elsif/else
    } // end method 'universalUnsignedLong()'

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj instanceof Value) {
            final Value that = (Value) obj;
            return this.blockValue == null
                ? this.longValue == that.longValue
                : this.blockValue == that.blockValue;
        } else {
            return false;
        } // end if/else
    } // end method 'equals(Object)'

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.blockValue != null
            ? this.blockValue.hashCode()
            : this.longValue;
    } // end method 'hashCode()'

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        if (this.blockValue == null) {
            return (isLong() ? "Value(long)=" : "Value(offset)=")
                + (this.longValue >> 1);
        } else {
            return "Value(block)=" + this.blockValue.toString();
        } // end if/else
    } // end method 'toString()'

} // end class 'Value'
